#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 12-Nov-09 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Drawing;

using Thn;
#endregion

namespace Thn.Drawing
{
    /// <summary>
    /// Provides a helper class allowing bi-directional conversion
    /// between RGB and HLS (Hue, Luminance, Saturation) colour
    /// models.  The HLS model is helpful if you want to produce
    /// darker or ligher shades of colours (by varying the Luminance)
    /// or if you want to produce a brigher or more washed out 
    /// variant (by changing the Saturation).  These operations are
    /// typically very difficult to do in the RGB space.
    /// </summary>
#if DEBUG
    public
#else
    public
#endif
 class VersatileColor
    {
        #region PRIVATES
        private byte alpha = 255;
        private byte red = 0;
        private byte green = 0;
        private byte blue = 0;

        private float hue = 0;
        private float luminance = 0;
        private float saturation = 0;
        private void ToHLS()
        {
            byte minval = Math.Min(red, Math.Min(green, blue));
            byte maxval = Math.Max(red, Math.Max(green, blue));

            float mdiff = (float)(maxval - minval);
            float msum = (float)(maxval + minval);

            luminance = msum / 510.0f;

            if (maxval == minval)
            {
                saturation = 0.0f;
                hue = 0.0f;
            }
            else
            {
                float rnorm = (maxval - red) / mdiff;
                float gnorm = (maxval - green) / mdiff;
                float bnorm = (maxval - blue) / mdiff;

                saturation = (luminance <= 0.5f) ? (mdiff / msum) : (mdiff / (510.0f - msum));

                if (red == maxval)
                {
                    hue = 60.0f * (6.0f + bnorm - gnorm);
                }
                if (green == maxval)
                {
                    hue = 60.0f * (2.0f + rnorm - bnorm);
                }
                if (blue == maxval)
                {
                    hue = 60.0f * (4.0f + gnorm - rnorm);
                }
                if (hue > 360.0f)
                {
                    hue = hue - 360.0f;
                }
            }
        }

        private void ToRGB()
        {
            if (saturation == 0.0)
            {
                red = (byte)(luminance * 255.0F);
                green = red;
                blue = red;
            }
            else
            {
                float rm1;
                float rm2;

                if (luminance <= 0.5f)
                {
                    rm2 = luminance + luminance * saturation;
                }
                else
                {
                    rm2 = luminance + saturation - luminance * saturation;
                }
                rm1 = 2.0f * luminance - rm2;
                red = ToRGB1(rm1, rm2, hue + 120.0f);
                green = ToRGB1(rm1, rm2, hue);
                blue = ToRGB1(rm1, rm2, hue - 120.0f);
            }
        }

        private byte ToRGB1(float rm1, float rm2, float rh)
        {
            if (rh > 360.0f)
            {
                rh -= 360.0f;
            }
            else if (rh < 0.0f)
            {
                rh += 360.0f;
            }

            if (rh < 60.0f)
            {
                rm1 = rm1 + (rm2 - rm1) * rh / 60.0f;
            }
            else if (rh < 180.0f)
            {
                rm1 = rm2;
            }
            else if (rh < 240.0f)
            {
                rm1 = rm1 + (rm2 - rm1) * (240.0f - rh) / 60.0f;
            }

            return (byte)(rm1 * 255);
        }

        #endregion

        #region PUBLISHED
        /// <summary>
        /// Gets or sets the Red component of the colour.
        /// </summary>
        public byte Red
        {
            get
            {
                return red;
            }
            set
            {
                red = value;
                ToHLS();
            }
        }

        /// <summary>
        /// Gets or sets the Green component of the colour.
        /// </summary>
        public byte Green
        {
            get
            {
                return green;
            }
            set
            {
                green = value;
                ToHLS();
            }
        }

        /// <summary>
        /// Gets or sets the Blue component of the colour.
        /// </summary>
        public byte Blue
        {
            get
            {
                return blue;
            }
            set
            {
                blue = value;
                ToHLS();
            }
        }

        /// <summary>
        /// Gets or sets the Luminance (0.0 to 1.0) of the colour.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">If the value is out of the acceptable range for luminance (0.0 to 1.0)</exception>
        public float Luminance
        {
            get
            {
                return luminance;
            }
            set
            {
                if (value < 0.0f) luminance = 0f;
                else if (value > 1.0f) luminance = 1f;
                else luminance = value;
                //if ((value < 0.0f) || (value > 1.0f))
                //{
                //    throw new ArgumentOutOfRangeException("Luminance", "Luminance must be between 0.0 and 1.0");
                //}
                //luminance = value;
                ToRGB();
            }
        }

        /// <summary>
        /// Gets or sets the Hue (0.0 to 360.0) of the colour.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">If the value is out of the acceptable range for hue (0.0 to 360.0)</exception>
        public float Hue
        {
            get
            {
                return hue;
            }
            set
            {
                if (value < 0.0f) hue = 0f;
                else if (value > 360.0f) hue = 1f;
                else hue = value;
                //if ((value < 0.0f) || (value > 360.0f))
                //{
                //    throw new ArgumentOutOfRangeException("Hue", "Hue must be between 0.0 and 360.0");
                //}
                //hue = value;
                ToRGB();
            }
        }

        /// <summary>
        /// Gets or sets the Saturation (0.0 to 1.0) of the colour.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">If the value is out of the acceptable range for saturation (0.0 to 1.0)</exception>
        public float Saturation
        {
            get
            {
                return saturation;
            }
            set
            {
                if (value < 0.0f) saturation = 0f;
                else if (value > 1.0f) saturation = 1f;
                else saturation = value;
                //if ((value < 0.0f) || (value > 1.0f))
                //{
                //    throw new ArgumentOutOfRangeException("Saturation", "Saturation must be between 0.0 and 1.0");
                //}
                //saturation = value;
                ToRGB();
            }
        }

        /// <summary>
        /// Gets or sets the Color as a System.Drawing.Color instance
        /// </summary>
        public Color Color
        {
            get
            {
                Color c = Color.FromArgb(alpha, red, green, blue);
                return c;
            }
            set
            {
                red = value.R;
                green = value.G;
                blue = value.B;
                alpha = value.A;
                ToHLS();
            }
        }

        #endregion

        #region PUBLIC
        /// <summary>
        /// Lightens the colour by the specified amount by modifying
        /// the luminance (for example, 0.2 would lighten the colour by 20%)
        /// </summary>
        public void LightenColor(float lightenBy)
        {
            luminance *= (1.0f + lightenBy);
            if (luminance > 1.0f)
            {
                luminance = 1.0f;
            }
            ToRGB();
        }

        /// <summary>
        /// Darkens the colour by the specified amount by modifying
        /// the luminance (for example, 0.2 would darken the colour by 20%)
        /// </summary>
        public void DarkenColor(float darkenBy)
        {
            luminance *= darkenBy;
            ToRGB();
        }
        #endregion

        #region CONSTRUCTORS
        /// <summary>
        /// Constructs an instance of the class from the specified
        /// System.Drawing.Color
        /// </summary>
        /// <param name="c">The System.Drawing.Color to use to initialise the class</param>
        public VersatileColor(Color c)
        {
            red = c.R;
            green = c.G;
            blue = c.B;
            alpha = c.A;
            ToHLS();
            //hue = c.GetHue();
            //luminance = c.GetBrightness();
            //saturation = c.GetSaturation();
        }

        /// <summary>
        /// Constructs an instance of the class with the specified hue, luminance and saturation values.
        /// </summary>
        /// <param name="hue">The Hue (between 0.0 and 360.0)</param>
        /// <param name="luminance">The Luminance (between 0.0 and 1.0)</param>
        /// <param name="saturation">The Saturation (between 0.0 and 1.0)</param>
        /// <exception cref="ArgumentOutOfRangeException">If any of the H,L,S values are out of the acceptable range (0.0-360.0 for Hue and 0.0-1.0 for Luminance and Saturation)</exception>
        public VersatileColor(float hue, float luminance, float saturation)
        {
            if ((saturation < 0.0f) || (saturation > 1.0f))
            {
                throw new ArgumentOutOfRangeException("Saturation", "Saturation must be between 0.0 and 1.0");
            }
            if ((hue < 0.0f) || (hue > 360.0f))
            {
                throw new ArgumentOutOfRangeException("Hue", "Hue must be between 0.0 and 360.0");
            }
            if ((luminance < 0.0f) || (luminance > 1.0f))
            {
                throw new ArgumentOutOfRangeException("Luminance", "Luminance must be between 0.0 and 1.0");
            }
            this.hue = hue;
            this.luminance = luminance;
            this.saturation = saturation;
            ToRGB();
        }

        /// <summary>
        /// Constructs an instance of the class with the specified red, green and blue values.
        /// </summary>
        /// <param name="red">The red component.</param>
        /// <param name="green">The green component.</param>
        /// <param name="blue">The blue component.</param>
        public VersatileColor(byte red, byte green, byte blue)
        {
            this.red = red;
            this.green = green;
            this.blue = blue;
        }

        /// <summary>
        /// Constructs an instance of the class using the settings of another instance.
        /// </summary>
        /// <param name="hlsrgb">The instance to clone.</param>
        public VersatileColor(VersatileColor col)
        {
            this.red = col.Red;
            this.blue = col.Blue;
            this.green = col.Green;
            this.luminance = col.Luminance;
            this.hue = col.Hue;
            this.saturation = col.Saturation;
        }

        /// <summary>
        /// Constructs a new instance of the class initialised to black.
        /// </summary>
        public VersatileColor()
        {
        }

        #endregion
    }
}
